<?php

namespace ly\traits;

trait TRedis {
	/**
	 * @var \ly\Redis
	 */
	protected $redis;
	/**
	 * @var array|string
	 */
	protected $lock_keys;

	/**
	 * 设置错误缓存+unlock
	 *
	 * @param  int     $code    code代码
	 * @param  string  $errMsg  msg数据
	 * @param  int     $expire  缓存过期时间
	 *
	 * @return array
	 */
	protected function lock_msg ($code, $errMsg, $expire = 10) {
		$data = msg($code, $errMsg);
		$this->lock_cache($data, $expire);

		return $data;
	}

	/**
	 * 读取或设置缓存+unlock
	 *
	 * @param  mixed  $value   缓存数据
	 * @param  int    $expire  缓存过期时间
	 *
	 * @return mixed|bool
	 */
	protected function lock_cache ($value = null, $expire = -1) {
		$keys = $this->lock_keys;
		if ($value === null) {
			try {
				if ($this->redis === null || !$this->redis->exists($keys)) return false;
				$ret = $this->redis->get($keys);

				return (false === $obj = is_json($ret, true)) ? $ret : $obj;
			} catch (\RedisException $e) {
				return false;
			}
		}

		if ($this->redis === null) return false;

		try {
			if ($this->redis->set($keys, is_array($value) ? arr2json($value) : $value, $expire)) {
				$this->unlock();

				return true;
			}
		} catch (\RedisException $e) {
		}

		return false;
	}

	/**
	 * @param  array|string  $keys
	 *
	 * @return bool
	 */
	protected function lock ($keys, $expire = 5) {
		if ($this->redis === null) return true;

		$this->lock_keys = is_array($keys) ? array_filter($keys) : $keys;
		try {
			return $this->redis->lock($keys, $expire);
		} catch (\RedisException $e) {
			return false;
		}
	}

	/**
	 * @param  array|string  $keys
	 *
	 * @return bool
	 */
	protected function unlock ($keys = null) {
		if ($this->redis === null) return false;

		if (is_empty($keys)) $keys = $this->lock_keys;
		try {
			if ($this->redis->unlock($keys)) {
				$this->lock_keys = null;

				return true;
			}
		} catch (\RedisException $e) {
		}

		return false;
	}

	/**
	 * @param  array|string   $keys
	 * @param  callable|null  $callback_cached  读取缓存成功回调，callable(array|string keys, mixed $data): mixed
	 * @param  callable       $callback_locked  锁成功回调，callable(array|string keys): void 必须需设置lock_cache或unlock
	 * @param  int            $expire_lock
	 *
	 * @return mixed
	 */
	protected function lock_block ($keys, $callback_cached, $callback_locked, $expire_lock = 5) {
		if (($callback_cached !== null && !is_callable($callback_cached)) || !is_callable($callback_locked))
			throw new \InvalidArgumentException('参数2和3必须是回调函数');
		if ($this->redis === null) return call_user_func($callback_locked, $keys);

		$this->lock_keys = $keys = is_array($keys) ? array_filter($keys) : $keys;
		$timeout         = $expire_lock + 3;
		$_start          = microtime(true);
		do {
			time_nanosleep(0, 1000 * 1000 * 10);
			if (false !== $ret = $this->lock_cache()) {
				return $callback_cached === null ? $ret : call_user_func($callback_cached, $keys, $ret);
			}
			if (microtime(true) - $_start > $timeout) return msg(-21, '程序异常');
		} while (!$this->lock($keys, $expire_lock));

		return call_user_func($callback_locked, $keys);
	}
}